package com.spontaneappnew.activities;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.facebook.Request;
import com.facebook.Response;
import com.facebook.Session;
import com.facebook.SessionState;
import com.facebook.UiLifecycleHelper;
import com.facebook.model.GraphMultiResult;
import com.facebook.model.GraphObject;
import com.facebook.model.GraphObjectList;
import com.facebook.model.GraphUser;
import com.facebook.widget.ProfilePictureView;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.location.LocationClient;
import com.spontaneappnew.NavDrawerActivityConfiguration;
import com.spontaneappnew.NavDrawerItem;
import com.spontaneappnew.Places;
import com.spontaneappnew.R;
import com.spontaneappnew.SurpriseMeActivity;

public abstract class AbstractNavDrawerActivity extends FragmentActivity 
												implements GooglePlayServicesClient.ConnectionCallbacks,
															GooglePlayServicesClient.OnConnectionFailedListener {
	private DrawerLayout mDrawerLayout;
    protected ActionBarDrawerToggle mDrawerToggle;
    private ListView mDrawerList;
    protected NavDrawerActivityConfiguration navConf ;
    
	private ProgressDialog mProgressDialog;
	protected int mProgressCounter;
    
	// a flag to enable FB session state change checks
	private boolean isResumed = false;

	private TextView userName;
	private ProfilePictureView profilePictureView;
	private static ArrayList<String> friendsUsingApp;
	
	protected LocationClient mLocationClient;
	protected boolean isLocationServicesAvailable = false;
	
	// result code for new permissions request. 
	// used it to decide whether to update a session's info in the Activty.onActivityResult() method
	private static final int REAUTH_ACTIVITY_CODE = 100;
	/*
     * Define a request code to send to Google Play services to check whether Google Play services(location)
     * are available on the user's device.
     * This code is returned in Activity.onActivityResult() method
     */
    private final static int CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000;
    private final static int FROM_SETTINGS_REQUEST_CODE = 9001;
    // Default value for checking if location services are available
    private final static int LOCATION_MODE_OFF_AS_DEFAULT = 0;
        
    // Abstarct Method
    protected abstract NavDrawerActivityConfiguration getNavDrawerConfiguration();
    
    /*
     * Facebook authentication state
     */
	private UiLifecycleHelper uiHelper;
	private Session.StatusCallback callback = 
	    new Session.StatusCallback() {
	    @Override
	    public void call(Session session, 
	            SessionState state, Exception exception) {
	        onSessionStateChange(session, state, exception);
	    }
	};
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // Create an instance of the FB uiHelper and pass it the listener
        uiHelper = new UiLifecycleHelper(this, callback);
        uiHelper.onCreate(savedInstanceState);
        
        // Used by inherited children, shows a loading progress circle
        mProgressDialog = new ProgressDialog(this);
        mProgressCounter = 0;
        
        // Navigation drawer setup
        navConf = getNavDrawerConfiguration();
        
        setContentView(navConf.getMainLayout()); 
        
        mDrawerLayout = (DrawerLayout) findViewById(navConf.getDrawerLayoutId());
        mDrawerList = (ListView) findViewById(navConf.getLeftDrawerId());
        mDrawerList.setAdapter(navConf.getBaseAdapter());
        mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
        
        this.initDrawerShadow();
        
        getActionBar().setDisplayHomeAsUpEnabled(true);
        getActionBar().setHomeButtonEnabled(true);
        
        mDrawerToggle = new ActionBarDrawerToggle(
                this, mDrawerLayout, getDrawerIcon(),
                navConf.getDrawerOpenDesc(),
                navConf.getDrawerCloseDesc()
                ) {
        	/*
             * Called when the user opens the drawer
             */
            public void onDrawerClosed(View view) {
                invalidateOptionsMenu();
            }
            
            /*
             * Called when the user opens the drawer
             */
            public void onDrawerOpened(View drawerView) {
            	// Hide keyboard if when drawer opens
            	InputMethodManager keyboard = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
            	if(keyboard != null){
            		keyboard.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
            	}
                invalidateOptionsMenu();
            }
        };
        mDrawerLayout.setDrawerListener(mDrawerToggle);
        
  	  	/*
         * Create a new location client, using the enclosing class to
         * handle callbacks.
         */
        mLocationClient = new LocationClient(this, this, this);
        
        friendsUsingApp = new ArrayList<String>();
        // Check for an open FB session
        final Session session = Session.getActiveSession();
        if (session != null && session.isOpened()) {
            // get the user's data
            makeMeRequest(session);
            // if FB friends list who use the app is null, create it
            if(Places.getInstance()!= null && Places.getInstance().getFriendsUsingAppList() == null){
	            new AsyncTask<Void, Void, Void>() {
					
					@Override
					protected Void doInBackground(Void... params) {
						friendsUsingApp = requestMyAppFacebookFriends(session);
						return null;
					}
					
					@Override
					protected void onPostExecute(Void result) {
						Places.getInstance().setFriendsList(friendsUsingApp);
						Places.getInstance().filterPlacesByFriends();
					}
				}.execute();
            }
        }
    }
    
    @Override
	protected void onStart() {
		super.onStart();
		// Check if location services are available. 
        // If not, show a dialog that alerts the user and takes him to the settings menu to change it.
        isLocationServicesAvailable();
	}
    
	@Override
	protected void onResume() {
        super.onResume();
        uiHelper.onResume();
        isResumed = true;
    }
    
    @Override
    protected void onPause() {
        super.onPause();
        uiHelper.onPause();
        isResumed = false;
    }
    
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
        	// Facebook UI code
			case REAUTH_ACTIVITY_CODE:
				uiHelper.onActivityResult(requestCode, resultCode, data);
			break;
			case CONNECTION_FAILURE_RESOLUTION_REQUEST :
				/*
	             * If the result code is Activity.RESULT_OK, try
	             * to connect again
	             */
	                switch (resultCode) {
	                    case Activity.RESULT_OK :
	                    	// Try the request again
	                    	servicesConnected();
	                    break;
	                }
			break;
			case FROM_SETTINGS_REQUEST_CODE:
				isLocationServicesAvailable();
				servicesConnected();
			break;
			default:
			break;
        }
    }
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        uiHelper.onDestroy();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        uiHelper.onSaveInstanceState(outState);
    }
    
    protected void initDrawerShadow() {
        mDrawerLayout.setDrawerShadow(navConf.getDrawerShadow(), GravityCompat.START);
    }
    
    protected int getDrawerIcon() {
        return R.drawable.ic_drawer;
    }
    
    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        mDrawerToggle.syncState();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mDrawerToggle.onConfigurationChanged(newConfig);
    }
    
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        if ( navConf.getActionMenuItemsToHideWhenDrawerOpen() != null ) {
            boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
            // hide specific actionbar items if needed
            for( int iItem : navConf.getActionMenuItemsToHideWhenDrawerOpen()) {
                menu.findItem(iItem).setVisible(!drawerOpen);
            }
            // hide an actionbar item's group if needed
            if(navConf.getIsActionMenuGroupToHideWhenDrawerOpen())
            	menu.setGroupVisible(navConf.getActionMenuGroupToHideWhenDrawerOpen(), !drawerOpen);
        }
        return super.onPrepareOptionsMenu(menu);
    }
    
    
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (mDrawerToggle.onOptionsItemSelected(item)) {
            return true;
        }
        else {
        	Intent intent;
        	switch (item.getItemId()) {
			case R.id.action_add_new_place:
				intent = new Intent(this, AddNewPlaceActivity.class);
				startActivity(intent);
				return true;
			case R.id.action_map_view:
                intent = new Intent(this, MapActivity.class);
                startActivity(intent);
                return true;
			case R.id.action_surprise:
				intent = new Intent(this, SurpriseMeActivity.class);
				startActivity(intent);
				return true;
			default:
				return super.onOptionsItemSelected(item);
			}
        }
    }
    
    // Handles the open and close navigation drawer when user clicks the menu
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if ( keyCode == KeyEvent.KEYCODE_MENU ) {
            if ( this.mDrawerLayout.isDrawerOpen(this.mDrawerList)) {
                this.mDrawerLayout.closeDrawer(this.mDrawerList);
            }
            else {
                this.mDrawerLayout.openDrawer(this.mDrawerList);
            }
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }
    
    protected DrawerLayout getDrawerLayout() {
        return mDrawerLayout;
    }

    protected ActionBarDrawerToggle getDrawerToggle() {
        return mDrawerToggle;
    }
   
    private class DrawerItemClickListener implements ListView.OnItemClickListener {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            selectItem(position);
        }
    }
    
    public void selectItem(int position) {
        NavDrawerItem selectedItem = navConf.getNavItems()[position];
        
        this.onNavItemSelected(selectedItem.getId());
        mDrawerList.setItemChecked(position, true);
        
        if ( this.mDrawerLayout.isDrawerOpen(this.mDrawerList)) {
            mDrawerLayout.closeDrawer(mDrawerList);
        }
    }
    
    // Handles the navigation items that are general for all activities using the drawer
    protected void onNavItemSelected(int id) {
		switch ((int)id) {
        // About nav button was pressed
        case 105:
        	AlertDialog.Builder builder = new AlertDialog.Builder(AbstractNavDrawerActivity.this);
			builder.setMessage(R.string.about_dialog)
			       .setCancelable(true)
			       .setPositiveButton("OK", new DialogInterface.OnClickListener() {
			           public void onClick(DialogInterface dialog, int id) {
			                dialog.dismiss();
			           }
			       });
			AlertDialog alert = builder.create();
			alert.show();
            break;
        // Rate Us nav button was pressed
        case 107:
        	Toast.makeText(this, R.string.rate_us_toast, Toast.LENGTH_SHORT).show();
        	break;
        // Logout nav button was pressed
	    case 108:
	        callFacebookLogout(this);
	        break;
	    }
    }
    
 ///////////////////// Facebook Session and Requests ///////////////////////////////
	
	/**
	 * Called due to FB login/logout session state changes.
	 * Shows the relevant fragment based on the session's state.
	 * 
	 */
	private void onSessionStateChange(Session session, SessionState state, Exception exception) {
    	if(isResumed){
			// Only make changes if the activity is visible
	        FragmentManager manager = getSupportFragmentManager();
	        // Get the number of entries in the back stack
	        int backStackSize = manager.getBackStackEntryCount();
	        // Clear the back stack
	        for (int i = 0; i < backStackSize; i++) {
	            manager.popBackStack();
	        }
	        if (session != null){
	        	if(session.isOpened()) {
		            // Get the user's data.
		            makeMeRequest(session);
	        	}
	        	else 
	        		callFacebookLogout(this);
	        }
    	}
	}
	
	/*
	 * Creates an async call to get the user's profile picture and user name
	 */
	private void makeMeRequest(final Session session) {
	    // Make an API call to get user data and define a 
	    // new callback to handle the response.
	    Request request = Request.newMeRequest(session, 
	            new Request.GraphUserCallback() {
	        @Override
	        public void onCompleted(GraphUser user, Response response) {
	            // If the response is successful
	            if (session == Session.getActiveSession()) {
	                if (user != null) {
	                	// Save the user's FB id in SharedPreferences
	                	SharedPreferences pref = getSharedPreferences("PREF", MODE_PRIVATE);
	                	String FBID = pref.getString("FBID", "");
	                	if(FBID.compareTo("") == 0){
	                		SharedPreferences.Editor editor = pref.edit();
	                		editor.putString("FBID", user.getId());
	                		editor.commit();
	                	}
	                    // Set the id for the ProfilePictureView
	                    // view that in turn displays the profile picture.
	                	profilePictureView = (ProfilePictureView) findViewById(R.id.nav_drawer_header_profile_pic);
	                	if(profilePictureView != null){
	                    	profilePictureView.setCropped(true);
	                    	profilePictureView.setProfileId(user.getId());
	                    }
	                    // Set the Textview's text to the user's name.
                    	userName = (TextView) findViewById(R.id.nav_drawer_header_user_name);
                    	if(userName != null){
	                    	userName.setText(user.getName());
	                    }
	                    // Set the user name on the nav drawer header
	                    navConf.getNavItems()[0].setLabel(user.getName());
	                }
	            }
	        }
	    });
	    request.executeAsync();
	} 
	
	/*
	 * Create a request to get a list of all fb friends with a boolean "installed" field
	 * that is true if the user also uses the app 
	 */
	private Request createRequest(Session session) {
	    Request request = Request.newGraphPathRequest(session, "me/friends", null);

	    Set<String> fields = new HashSet<String>();
	    String[] requiredFields = new String[] { "id", "name", "installed" };
	    fields.addAll(Arrays.asList(requiredFields));

	    Bundle parameters = request.getParameters();
	    parameters.putString("fields", TextUtils.join(",", fields));
	    request.setParameters(parameters);

	    return request;
	}
	
	/*
	 * Make a request (always called from a worker thread) to get the friends list
	 */
	private ArrayList<String> requestMyAppFacebookFriends(Session session) {
		final ArrayList<String> friendsUsingApp = new ArrayList<String>();
	    Request friendsRequest = createRequest(session);
	    friendsRequest.setCallback(new Request.Callback() {

	        @Override
	        public void onCompleted(Response response) {
	            List<GraphUser> friends = getResults(response);
	            for (GraphUser friend : friends) {
					if(friend.getProperty("installed") != null && (Boolean)friend.getProperty("installed") == true){
						friendsUsingApp.add(friend.getId());
						friendsUsingApp.add((String)friend.getProperty("name"));
					}
				}
	        }
	    });
	    friendsRequest.executeAndWait();
		return friendsUsingApp;
	}
	
	/*
	 * Translates the friends list to GraphUser list so we can work with it
	 */
	private List<GraphUser> getResults(Response response) {
	    GraphMultiResult multiResult = response
	            .getGraphObjectAs(GraphMultiResult.class);
	    GraphObjectList<GraphObject> data = multiResult.getData();
	    return data.castToListOf(GraphUser.class);
	}
	
	/*
	 * When the logout button in the nav drawer is pressed, logs out of FB and goes to login screen.
	 */
	public void callFacebookLogout(Context context) {
		// clear the SharedPrefs key value of FB user ID
		SharedPreferences pref = getSharedPreferences("PREF", MODE_PRIVATE);
		SharedPreferences.Editor editor = pref.edit();
		editor.remove("FBID");
		editor.commit();
		// logout user and start LoginActivity
		Intent intent = new Intent(this, LoginActivity.class);
	    Session session = Session.getActiveSession();
	    if (session != null) {
	        if (!session.isClosed()) {
	        	//clear your preferences if saved
	            session.closeAndClearTokenInformation();
	        }
	    } else {
	        session = new Session(context);
	        Session.setActiveSession(session);

	        //clear preferences if saved
	        session.closeAndClearTokenInformation();
	    }
	    startActivity(intent);
	    finish();
	}
	
	 ///////////////////// Google Play Services and Location Services (location uses) ///////////////////////////////
	
	/*
	 * Checks if the Google Play services are available, return true/false accordingly
	 * If the services are not available show a dialog helping the user to get it to work
	 * Through sending him to enable it or to the Play store
	 */
	protected boolean servicesConnected() {
        // Check that Google Play services is available
        int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
        // If Google Play services is available
        if (ConnectionResult.SUCCESS == resultCode) {
            // Continue
            return true;
        // Google Play services was not available for some reason
        } else {
        	int errorCode = resultCode;
            // Get the error dialog from Google Play services
            Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(
                    errorCode, this, CONNECTION_FAILURE_RESOLUTION_REQUEST);
            // If Google Play services can provide an error dialog
            if (errorDialog != null) {
                // Create a new DialogFragment for the error dialog
                ErrorDialogFragment errorFragment =
                        new ErrorDialogFragment();
                // Set the dialog in the DialogFragment
                errorFragment.setDialog(errorDialog);
                // Show the error dialog in the DialogFragment
                errorFragment.show(getSupportFragmentManager(),
                        "Location Updates");
            }
            return false;
        }
    }
	
	// Define a DialogFragment that displays the error dialog
    public static class ErrorDialogFragment extends DialogFragment {
        // Global field to contain the error dialog
        private Dialog mDialog;
        // Default constructor. Sets the dialog field to null
        public ErrorDialogFragment() {
            super();
            mDialog = null;
        }
        // Set the dialog to display
        public void setDialog(Dialog dialog) {
            mDialog = dialog;
        }
        // Return a Dialog to the DialogFragment.
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            return mDialog;
        }
    }
    
    /*
     * Called by Location Services if the attempt to
     * Location Services fails.
     */
    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        /*
         * Google Play services can resolve some errors it detects.
         * If the error has a resolution, try sending an Intent to
         * start a Google Play services activity that can resolve
         * error.
         */
        if (connectionResult.hasResolution()) {
            try {
                // Start an Activity that tries to resolve the error
                connectionResult.startResolutionForResult(
                        this,
                        CONNECTION_FAILURE_RESOLUTION_REQUEST);
                /*
                 * Thrown if Google Play services canceled the original
                 * PendingIntent
                 */
            } catch (IntentSender.SendIntentException e) {
                // Log the error
                e.printStackTrace();
            }
        } else {
            /*
             * If no resolution is available, display a dialog to the
             * user with the error.
             */
            showErrorDialog(connectionResult.getErrorCode());
        }
    }
    
    /*
     * Gets an error code and show an AlertDialog accordingly.
     * Helper function for the location services from Google Play services when trying to connect
     * and recieve an error
     */
    protected void showErrorDialog(int errorCode) {
        new AlertDialog.Builder(this)
                .setMessage(String.valueOf(errorCode))
                .setPositiveButton(android.R.string.ok,
                        new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int id) {
                                dialog.dismiss();
                            }
                        }).show();
    }
    
     /*
      * Check if location services are available. 
      * If not, show a dialog that alerts the user and takes him to the settings menu to change it.
      */
    @SuppressLint("InlinedApi")
	protected void isLocationServicesAvailable(){
    	// Checks the user's API - if >= KITKAT, use 'LOCATION_MODE', else use 'LOCATION_PROVIDERS_ALLOWED'
    	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
	    	int locationProviders = Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE, LOCATION_MODE_OFF_AS_DEFAULT);
	    	switch (locationProviders) {
	    	case Settings.Secure.LOCATION_MODE_OFF:
	    		showLocationServicesOffDialog();
	    		break;
	    	case Settings.Secure.LOCATION_MODE_SENSORS_ONLY:
	    		showLocationServicesOffDialog();
	    		break;
	    	case Settings.Secure.LOCATION_MODE_BATTERY_SAVING:
	    		showLocationServicesOffDialog();
	    		break;
	    	default:
	    		isLocationServicesAvailable = true;
	    		break;
	    	}
    	}
    	// User has API < KITKAT(19)
    	else{
    		@SuppressWarnings("deprecation")
			String locationProviders = Settings.Secure.getString(getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
    		if (locationProviders == null || locationProviders.equals("")) {
    			showLocationServicesOffDialog();
    		}
    		else{
    			isLocationServicesAvailable = true;
    		}
    	}
    }
    
    /* Shows a dialog to alert the user that his location services are disabled and take him to the settings
     * if he wants to.
     */ 
    private void showLocationServicesOffDialog(){
    	String title = "Location services disabled";
    	String msg = "SpontaneApp needs access to your location.\nPlease turn on location access.";
    	AlertDialog.Builder builder = new AlertDialog.Builder(AbstractNavDrawerActivity.this);
		builder.setMessage(msg)
				.setTitle(title)
				.setCancelable(false)
				.setPositiveButton("Settings", new DialogInterface.OnClickListener() {
		        	public void onClick(DialogInterface dialog, int id) {
		        	   // Go to phone's Location settings screen
		        		dialog.dismiss();
		        	   startActivityForResult(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), FROM_SETTINGS_REQUEST_CODE);
		        	}
				})
				.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
		        	public void onClick(DialogInterface dialog, int id) {
		        	    // Dismiss dialog and state of boolean field to false
		        		isLocationServicesAvailable = false;
		        		dialog.dismiss();
		        	}
				});
		AlertDialog alert = builder.create();
		alert.show();
    }
    
///////////////////// Progress Dialog Circle methods ///////////////////////////////
    
    // Sets up a progress dialog with a spinner to show when performing stuff in the background
    protected void showProgressCircle(){
    	// check if there is no other action that already started the progressCircle, if I'm the first then start it
    	if(mProgressCounter == 0){
	    	mProgressDialog.setMessage("Loading");
	    	mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
	    	mProgressDialog.setIndeterminate(true);
	    	mProgressDialog.setCanceledOnTouchOutside(false);
	    	mProgressDialog.show();
    	}
    	mProgressCounter++;
    }
    
    // Hides the progress dialog when done performing stuff in the background
    protected void hideProgressCircle(){
    	// dec counter by one and check if any other action still needs the progressCircle
    	mProgressCounter--;
    	if(mProgressCounter == 0)
    		mProgressDialog.hide();
    }
    

    @Override
	public void onConnected(Bundle bundle) {}

	@Override
	public void onDisconnected() {}
	
}
